package unipd.esp1112.pro;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Vibrator;


/**
 * 
 * Service used by Misurazioni and MisurazioniSuMisura
 * 
 * @author andrea basso, federico lancerin, alex tomasello
 * 
 */
public final class Servizi extends Service {

	/**
	 * int used by threads launched by the activity to do different tasks at different stages of the recording process
	 */
	private int step;
	/**
	 * int value of "step" variable that represents that the service is waiting for GPS signal
	 */
	private final int INATTESA = 0;
	/**
	 * int value of "step" variable that represents that the service is currently recording data 
	 */
	private final int REGISTRA = 1;
	/**
	 * radius of the earth in meters
	 */
	private final double RAGGIO = 6372795.477598;

	/**
	 * timer used as internal chrono
	 */
	private Timer timer;
	int secondi;
	int minuti;
	int secondiTotali;
	int ore;

	/**
	 * boolean variable true if training limit is reached
	 */
	private boolean limitReached;
	/**
	 * boolean flag used to disable the warning launched every second by the service when the limit is reached
	 */
	private boolean warningDisabled;
	/**
	 * the actuator of the warning
	 */
	private Vibrator vibro;
	
	/*
	 * 	VARIABLES USED BY THE SERVICE TO ACQUIRE THE SYSTEM WAKELOCK
	 */
	PowerManager pwrmgr;
	PowerManager.WakeLock lock;

	/*
	 * 	VARIABLES USED BY THE SERVICE TO ACCESS THE SYSTEM GPS SERVICE
	 */	
	private LocationListener mlocListener;
	private LocationManager mlocManager;
	/**
	 * boolean value checked by main activities to know when GPS had the first fix (and thus is ready to start recording)
	 */
	private boolean gotFix;
	/**
	 * boolean variable true if GPS provider is enable
	 */
	private boolean providerEnabled;
	/**
	 * int value where the number of satellites used for the fix is stored (when GPS provider can provide it)
	 */
	private int satellitiUsati;
	/**
	 * variable used to store the value of instantaneous speed (in m/s)
	 */
	private double velocitaIstantanea;
	/**
	 * variable used to store the value of the altitude in meters from the sea level
	 */
	private double altitudine;
	
	// 	VARIABLES USED BY CALORIES CALCULATING METHOD
	
	/** 
	 * value of calories burned since the last GPS call
	 */
	private double calorie;
	/**
	 * variable used in the process of determining how many calories you are burning
	 */
	private double istSpeed;
	/**
	 * variable used in the process of determining how many calories you are burning
	 */
	private double costante;
	/**
	 * variable used in the process of determining how many calories you are burning
	 */
	private int secondiTrascorsi;
	
	// VARIABLES TO STORE AND ADJUST REGISTRATION DATA
	
	/**  
	 * string where the main activities can find a representation of the time elapsed in HH:MM:SS
	 */
	private String tempo;	
	/**
	 * int that stores the actual travel distance
	 */
	private int distanzaIncrementante;
	/**
	 * variable used to store the value of burned calories (in Kcal) 
	 */
	private double calorieConsumate;
	/**
	 * average speed in Km/h
	 */
	private double velocitaMedia;
	/**
	 * int that represent the % advance of the training
	 */
	private int completamentoPercentuale;
	
	// PROFILE VARIABLES
	
	/** 
	 * boolean true if user is male, false if female
	 */
	private boolean isMale;
	/**
	 * int that stores the user height
	 */
	private int altezza;
	/**
	 * int that stores the user weight
	 */
	private int peso;
	/**
	 * int that stores the user age
	 */
	private int anni;
	
	// VARIABLES WHERE THE ACTUAL LIMIT AND LIMIT TYPE ARE STORED
	
	/** 
	 * string representing the actual training method
	 */
	private String tipoAttivita;
	/**
	 * int storing the actual training limit
	 */
	private int limiteImpostato;
	
	/**
	 * IBinder used to instantiate a Binder
	 */
	private final IBinder maniglia = new ServiziBinder();

	// OVERRIDDEN METHODS
	
	@Override
	public void onCreate() {
		System.out.println("onCreateService");

		step = INATTESA;
		tempo = new String();
		distanzaIncrementante = 0;
		velocitaMedia = 0;
		velocitaIstantanea = 0;
		altitudine = 0;
		satellitiUsati = 0;
		velocitaMedia = 0;
		altitudine = 0;
		calorieConsumate = 0;
		ore = 0;
		minuti = 0;
		secondiTotali = 0;
		secondi = 0;
		timer = new Timer();
		limitReached = false;
		warningDisabled = false;
		gotFix = false;

		pwrmgr = (PowerManager) getSystemService(this.POWER_SERVICE);
		lock = pwrmgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Service WakeLock");
		lock.acquire();

		// inizializzo il GPS in modo che cominci ad aggiornare alcune delle
		// variabili su cui opera
		mlocListener = new MyLocationListener();
		mlocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000L, 0, mlocListener);
		providerEnabled = mlocManager.isProviderEnabled(mlocManager.GPS_PROVIDER);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startid) {
		System.out.println("onStartService");
		vibro = (Vibrator) getSystemService(VIBRATOR_SERVICE);
		step++;
		timer.schedule(Tick, 1000, 1000);
		return START_STICKY;
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		System.out.println("onDestroyService");
		mlocManager.removeUpdates(mlocListener);
		lock.release();
		timer.cancel();
	}

	// UTILITY METHODS
	
	/**
	 * IBinder used to make the connection between an activity and thi service.
	 */
	public IBinder onBind(Intent intent) {
		return maniglia;
	}

	/**
	 * timertask used to update the service internal clock and all of its variables.
	 */
	private TimerTask Tick = new TimerTask() {
		@Override
		public void run() {
			secondi++;
			secondiTotali++;
			if (secondi >= 60) {
				minuti++;
				secondi = 0;
			}
			if (minuti >= 60) {
				ore++;
				minuti = 0;
			}
			updateTime();

			velocitaMedia = calcolaVelocitaMedia();

			if (tipoAttivita.equalsIgnoreCase("tempo") || tipoAttivita.equalsIgnoreCase("pausa"))
				completamentoPercentuale = (int) ((secondiTotali * 100) / (limiteImpostato));
			if (tipoAttivita.equalsIgnoreCase("distanza"))
				completamentoPercentuale = (int) (distanzaIncrementante * 100 / limiteImpostato);
			if (tipoAttivita.equalsIgnoreCase("calorie"))
				completamentoPercentuale = (int) (calorieConsumate * 100 / limiteImpostato);

			// verifica il raggiungimento del limite preimpostato (se presente)
			if (!limitReached && (tipoAttivita.equalsIgnoreCase("tempo") || tipoAttivita.equalsIgnoreCase("pausa"))
					&& (secondiTotali >= limiteImpostato))
				limitReached = true;
			if (!limitReached && tipoAttivita.equalsIgnoreCase("distanza") && (distanzaIncrementante >= limiteImpostato))
				limitReached = true;
			if (!limitReached && tipoAttivita.equalsIgnoreCase("calorie") && (calorieConsumate >= limiteImpostato))
				limitReached = true;
			if (limitReached && !warningDisabled) {
				vibro.vibrate(300);
				Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
				Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
				r.play();
			}
		}
	};
	
	/**
	 * method called to update the internal clock string representation.
	 */
	public void updateTime() {
		if (minuti < 10 && secondi >= 10)
			tempo = (ore + ":0" + minuti + ":" + secondi);
		else if (minuti >= 10 && secondi < 10)
			tempo = (ore + ":" + minuti + ":0" + secondi);
		else if (minuti < 10 && secondi < 10)
			tempo = (ore + ":0" + minuti + ":0" + secondi);
		else
			tempo = (ore + ":" + minuti + ":" + secondi);
	}

	/**
	 * method called each second to update the internal medium speed.
	 * 
	 * @return medium speed
	 */
	private double calcolaVelocitaMedia() {
		return (((double) distanzaIncrementante) / ((double) secondiTotali)) * 3.6; 
	}

	/**
	 * method called to compute distance between 2 geographical points given those points latitude, longitude, altitude.
	 * 
	 * @param latitudeFrom
	 * @param longitudeFrom
	 * @param latitudeTo
	 * @param longitudeTo
	 * @param altitudeFrom
	 * @param altitudeTo
	 * @return
	 */
	private int calcolaDistanza(double latitudeFrom, double longitudeFrom, double latitudeTo, double longitudeTo, double altitudeFrom,
			double altitudeTo) {

		double radLatA = Math.PI * latitudeFrom / 180;
		double radLonA = Math.PI * longitudeFrom / 180;
		double radLatB = Math.PI * latitudeTo / 180;
		double radLonB = Math.PI * longitudeTo / 180;

		double distLong = (RAGGIO + altitudeFrom) * Math.sin(radLatA) * (radLonA - radLonB);
		double distLat = (RAGGIO + altitudeFrom) * (radLatA - radLatB);
		double distAlt = altitudeFrom - altitudeTo;

		double distanzapiano = Math.sqrt(Math.pow(distLong, 2) + Math.pow(distLat, 2));
		double distanza = Math.sqrt(Math.pow(distanzapiano, 2) + Math.pow(distAlt, 2));

		return (int) distanza;
	}

	/**
	 * method called on location upgrade to compute the calories consumed between this fix and the previous one
	 * given those 2 locations latitude, longitude, altitude, instantaneous speed of the last fix and the previous fix instant.
	 * It uses Harris & Benedict formula.
	 * 
	 * @param latitudeFrom
	 * @param longitudeFrom
	 * @param latitudeTo
	 * @param longitudeTo
	 * @param altitudeFrom
	 * @param altitudeTo
	 * @param speed
	 * @param lastOre
	 * @param lastMinuti
	 * @param lastSecondi
	 * @return calories consumed
	 */
	private double calcolaCalorie(double latitudeFrom, double longitudeFrom, double latitudeTo, double longitudeTo, double altitudeFrom,
			double altitudeTo, double speed, int lastOre, int lastMinuti, int lastSecondi) {

		/* 
		 * VELOCITA kMaschio kFemm x<5 km/h 0,6 0.55 5<x<8 km/h 0,8 0,75 8<x<12 km/h 0,95 0,9 x>12 km/h 1 0,95
		 * 
		 * Harris & Benedict: UOMO FORMULA TOT=(k*PESO*KM)+(66,4730 + (13,7156 * PESO) + (5,033* ALTEZZA) - (6,775 * ETA))/TEMPO
		 * 
		 * DONNA FORMULA TOT=(K*PESO*KM)+ (655,095 + (9,5634 * W) + (1,849 * H) - (4,6756 * A))/TEMPO
		 */

		calorie = 0;
		costante = 0;
		istSpeed = speed * 3.6;// da m/s a km/h
		secondiTrascorsi = 0;

		System.out.print("speed : " + istSpeed);

		if (istSpeed < 5)
			costante = 0.6;
		else if (istSpeed >= 5 && istSpeed < 8)
			costante = 0.8;
		else if (istSpeed >= 8 && istSpeed < 12)
			costante = 0.95;
		else
			costante = 1;

		double kmPercorsi = calcolaDistanza(latitudeFrom, longitudeFrom, latitudeTo, longitudeTo, altitudeFrom, altitudeTo) / 1000;

		if (secondi - lastSecondi < 0) {
			secondiTrascorsi = secondi + 60 - lastSecondi;
			if (minuti - lastMinuti < 0) {
				secondiTrascorsi = secondiTrascorsi + (minuti - 1 + 60 - lastMinuti) * 60 + (ore - 1 - lastOre) * 3600;
			} else
				secondiTrascorsi = secondiTrascorsi + (minuti - 1 - lastMinuti) * 60 + (ore - lastOre) * 3600;
		} else {
			secondiTrascorsi = secondi - lastSecondi;
			if (minuti - lastMinuti < 0) {
				secondiTrascorsi = secondiTrascorsi + (minuti + 60 - lastMinuti) * 60 + (ore - 1 - lastOre) * 3600;
			} else
				secondiTrascorsi = secondiTrascorsi + (minuti - lastMinuti) * 60 + (ore - lastOre) * 3600;
		}

		if (isMale)
			calorie = (costante * kmPercorsi * peso) + ((66.4730 + (13.7156 * peso) + (5.033 * altezza) - (6.775 * anni)) / 86400) * (secondiTrascorsi);
		else
			calorie = (costante * kmPercorsi * peso) + ((655.095 + (9.5634 * peso) + (1.849 * altezza) - (4.6756 * anni)) / 86400) * (secondiTrascorsi);

		return calorie;
	}
	
	// METHODS CALLED FROM MAIN ACTIVITY

	/**
	 * method called from the main activity to set user data. 
	 * 
	 * @param sex
	 * @param age
	 * @param height
	 * @param weight
	 */
	public void setProfileData(boolean sex, int age, int height, int weight) {
		isMale = sex;
		anni = age;
		altezza = height;
		peso = weight;
	}

	/**
	 * method called from the main activity to set training parameters.
	 * 
	 * @param attivita
	 * @param valore
	 */
	public void setTrainingParameters(String attivita, int valore) {
		tipoAttivita = attivita;
		limiteImpostato = valore;
	}

	/**
	 * method called from the main activity to check if the limit has been reached.
	 * 
	 * @return
	 */
	public boolean hasLimitBeenReached() {
		return limitReached;
	}

	/**
	 * method called from the main activity to re-set this semaphore after a limit is reached if there is another one. 
	 * 
	 * @param value
	 */
	public void setLimitReached(boolean value) {
		limitReached = value;
	}

	/**
	 * method called from the main activity to disable audio and vibration warnings when a limit is reached.
	 */
	public void disableWarning() {
		warningDisabled = true;
	}

	/**
	 * method called from the main activity to re-enable warnings when passing from a finished training activity to
	 * the next one.
	 */
	public void enableWarning() {
		warningDisabled = false;
	}

	/**
	 * method called from the main activity to check if the GPS provider is enabled.
	 * 
	 * @return
	 */
	public boolean isProviderEnabled() {
		return providerEnabled;
	}

	/**
	 * method called from the main activity to update his clock with the one used by the service.
	 * 
	 * @return clock
	 */
	public String getTime() {
		return tempo;
	}

	/**
	 * method called from the main activity to update his internal distance. 
	 * 
	 * @return distance
	 */
	public String getDistance() {
		return "" + distanzaIncrementante;
	}

	/**
	 * method called from the main activity to update his internal calories. 
	 * 
	 * @return calories
	 */
	public String getCalories() {
		return "" + ((int) (calorieConsumate * 100)) / 100;
	}

	/**
	 * method called from the main activity to update his internal medium speed. 
	 * 
	 * @return medium speed
	 */
	public String getMedSpeed() {
		return "" + String.format("%.2f", velocitaMedia);
	}

	/**
	 * method called from the main activity to update his internal completion %. 
	 * 
	 * @return completion %
	 */
	public String getPercent() {
		return "" + completamentoPercentuale;
	}

	/**
	 * method called from the main activity to update his internal total seconds variable. 
	 * 
	 * @return total seconds
	 */
	public int getTotSec() {
		return secondiTotali;
	}

	/**
	 * method called from the main activity to update his internal # of satellites used. 
	 * 
	 * @return # of satellites used for the fix
	 */
	public int getNumSatellites() {
		return satellitiUsati;
	}

	/**
	 * method called from the main activity to check if the first fix has already been registered. 
	 * 
	 * @return true if the first fix has already been registered
	 */
	public boolean checkFix() {
		return gotFix;
	}

	/**
	 * method called from the main activity to update his internal instantaneous speed variable. 
	 * 
	 * @return instantaneous speed
	 */
	public String getIstSpeed() {
		return "" + ((int) (velocitaIstantanea) * 100) / 100;
	}

	/**
	 * method called from the main activity to update his internal altitude. 
	 * 
	 * @return altitude
	 */
	public double getAltitude() {
		return altitudine;
	}

	/**
	 * inner class needed to make a connection from an activity to this service.
	 */
	public class ServiziBinder extends Binder {
		Servizi getService() {
			return Servizi.this;
		}
	}
	
	/**
	 * inner class needed by the service to access the system GPS provider.
	 */
	private class MyLocationListener implements LocationListener

	{
		private double lastLatitude = 0;
		private double lastLongitude = 0;
		private double lastAltitude = 0;
		private int lastOra = 0;
		private int lastMinuto = 0;
		private int lastSecondo = 0;

		public void onLocationChanged(Location loc) {
			providerEnabled = true;
			gotFix = true;
			if (step == REGISTRA) {
				distanzaIncrementante += calcolaDistanza(lastLatitude, lastLongitude, loc.getLatitude(), loc.getLongitude(), lastAltitude,
						loc.getAltitude());
				calorieConsumate += calcolaCalorie(lastLatitude, lastLongitude, loc.getLatitude(), loc.getLongitude(), lastAltitude, loc.getAltitude(),
						loc.getSpeed(), lastOra, lastMinuto, lastSecondo);
				lastOra = ore;
				lastMinuto = minuti;
				lastSecondo = secondi;
			}
			lastLatitude = loc.getLatitude();
			lastLongitude = loc.getLongitude();
			lastAltitude = loc.getAltitude();
			satellitiUsati = loc.getExtras().getInt("satellites");
			altitudine = loc.getAltitude();
			velocitaIstantanea = loc.getSpeed();
		}

		public void onProviderDisabled(String provider) {
			providerEnabled = false;
			gotFix = false;
		}

		public void onProviderEnabled(String provider) {
			providerEnabled = true;
		}

		public void onStatusChanged(String provider, int status, Bundle extras) {
		}
	}
	
	
}